#include <windows.h>

#include <assert.h>
#include <stddef.h>
#include <stdint.h>

#include "hook/iobuf.h"

#include "iccard/felica.h"

#include "util/dprintf.h"
#include "util/dump.h"

static HRESULT felica_cmd_poll(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res);

static HRESULT felica_cmd_get_system_code(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res);

static HRESULT felica_cmd_nda_a4(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res);

uint64_t felica_get_generic_PMm(void)
{
    /* A FeliCa PMm contains low-level protocol timing information for
       communicating with a particular IC card. The exact values are not
       particularly important for our purposes, so we'll just return a hard-
       coded PMm. This was taken from a SuiCa train pass I bought in Japan. */

    return 0x053143454682B7FFULL;
}

HRESULT felica_transact(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res)
{
    uint64_t IDm;
    uint8_t code;
    HRESULT hr;

    assert(f != NULL);
    assert(req != NULL);
    assert(res != NULL);

    hr = iobuf_read_8(req, &code);

    if (FAILED(hr)) {
        return hr;
    }

    hr = iobuf_write_8(res, code + 1);

    if (FAILED(hr)) {
        return hr;
    }

    if (code != FELICA_CMD_POLL) {
        hr = iobuf_read_be64(req, &IDm);

        if (FAILED(hr)) {
            return hr;
        }

        if (IDm != f->IDm) {
            return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
        }

        hr = iobuf_write_be64(res, IDm);

        if (FAILED(hr)) {
            return hr;
        }
    }

    switch (code) {
    case FELICA_CMD_POLL:
        return felica_cmd_poll(f, req, res);

    case FELICA_CMD_GET_SYSTEM_CODE:
        return felica_cmd_get_system_code(f, req, res);

    case FELICA_CMD_NDA_A4:
        return felica_cmd_nda_a4(f, req, res);

    default:
        dprintf("FeliCa: Unimplemented command %02x, payload:\n", code);
        dump_const_iobuf(req);

        return E_NOTIMPL;
    }
}

static HRESULT felica_cmd_poll(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res)
{
    uint16_t system_code;
    uint8_t request_code;
    uint8_t time_slot;
    HRESULT hr;

    /* Request: */

    hr = iobuf_read_be16(req, &system_code);

    if (FAILED(hr)) {
        return hr;
    }

    hr = iobuf_read_8(req, &request_code);

    if (FAILED(hr)) {
        return hr;
    }

    hr = iobuf_read_8(req, &time_slot);

    if (FAILED(hr)) {
        return hr;
    }

    if (system_code != 0xFFFF && system_code != f->system_code) {
        return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    }

    // TODO handle other params correctly...

    /* Response: */

    hr = iobuf_write_be64(res, f->IDm);

    if (FAILED(hr)) {
        return hr;
    }

    hr = iobuf_write_be64(res, f->PMm);

    if (FAILED(hr)) {
        return hr;
    }

    if (request_code == 0x01) {
        hr = iobuf_write_be16(res, f->system_code);

        if (FAILED(hr)) {
            return hr;
        }
    }

    return S_OK;
}

static HRESULT felica_cmd_get_system_code(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res)
{
    HRESULT hr;

    hr = iobuf_write_8(res, 1); /* Number of system codes */

    if (FAILED(hr)) {
        return hr;
    }

    hr = iobuf_write_be16(res, f->system_code);

    if (FAILED(hr)) {
        return hr;
    }

    return S_OK;
}

static HRESULT felica_cmd_nda_a4(
        struct felica *f,
        struct const_iobuf *req,
        struct iobuf *res)
{
    /* The specification for this command is probably only available under NDA.
       Returning what the driver seems to want. */

    return iobuf_write_8(res, 0);
}
